package timbap.visualizer.impl.context.messaging;

import oscP5.OscEventListener;
import oscP5.OscMessage;
import oscP5.OscStatus;
import timbap.visualizer.api.context.messaging.AbstractTimeCodePublisher;
import timbap.visualizer.api.model.TimeCode;

public class OscTimeCodePublisherImpl extends AbstractTimeCodePublisher implements OscEventListener {

    private static final String OSC_POWER_ADDRESS = "/timbap_power";
    private static final String OSC_VELOCITY_ADDRESS = "/timbap_velocity";
    private static final String OSC_ABSPOSITION_ADDRESS = "/timbap_position";
    private static final String OSC_FRACPLAYED_ADDRESS = "/played";
    private static final int STALE_TIME_IN_MS = 1000;

    private TimeCode timeCode;
    private boolean hasTimeCode = false;
    private long lastReceiveTime;

    public OscTimeCodePublisherImpl(Class<? extends TimeCode> pTimeCodeClass) {
        super();
        try {
            timeCode = pTimeCodeClass.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * handles a message received over OSC
     * Please note that due to a bug in OscP5 
     * you ALWAYS have to override this method
     * if you derive from this class
     *
     * @param pOscMessage   the message received
     */
    public void oscEventXX(OscMessage pOscMessage) {
        try {
        if (pOscMessage != null) {
            String tAddrPattern = pOscMessage.addrPattern();
            Object[] tArguments = pOscMessage.arguments();
            if (tAddrPattern != null && tArguments != null) {
                if (tArguments.length == 1 && tArguments[0] instanceof Float) {
                    float tValue = (Float)tArguments[0];
                    if (tAddrPattern.equals(OSC_POWER_ADDRESS)) {
                        timeCode.setSignalPower(tValue);
                    } else if (tAddrPattern.equals(OSC_VELOCITY_ADDRESS)) {
                        timeCode.setVelocity(tValue);
                    } else if (tAddrPattern.equals(OSC_ABSPOSITION_ADDRESS)) {
                        timeCode.setAbsolutePosition(tValue);
                    } else if (tAddrPattern.equals(OSC_FRACPLAYED_ADDRESS)) {
                        timeCode.setFractionPlayed(tValue);
                    }
                    if (!hasTimeCode) {
                        hasTimeCode = true;
                    }
                    lastReceiveTime = System.currentTimeMillis();
                    publishTimeCode(timeCode);
                }
            }
        }
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    public void oscEvent(OscMessage pOscMessage) {
        try {
        if (pOscMessage != null) {
            String tAddrPattern = pOscMessage.addrPattern();
            Object[] tArguments = pOscMessage.arguments();
            if (tAddrPattern != null && tArguments != null) {
                if (tArguments.length == 4 && tAddrPattern.equals("/timbap_timecode")) {
                    timeCode.setSignalPower((Float)tArguments[0]);
                    timeCode.setVelocity((Float)tArguments[1]);
                    timeCode.setAbsolutePosition((Float)tArguments[2]);
                    timeCode.setFractionPlayed((Float)tArguments[3]);
                    if (!hasTimeCode) {
                        hasTimeCode = true;
                    }
                    lastReceiveTime = System.currentTimeMillis();
                    publishTimeCode(timeCode);
                } else if (tArguments.length == 1 && tArguments[0] instanceof Float) {
                    float tValue = (Float)tArguments[0];
                    if (tAddrPattern.equals(OSC_POWER_ADDRESS)) {
                        timeCode.setSignalPower(tValue);
                    } else if (tAddrPattern.equals(OSC_VELOCITY_ADDRESS)) {
                        timeCode.setVelocity(tValue);
                    } else if (tAddrPattern.equals(OSC_ABSPOSITION_ADDRESS)) {
                        timeCode.setAbsolutePosition(tValue);
                    } else if (tAddrPattern.equals(OSC_FRACPLAYED_ADDRESS)) {
                        timeCode.setFractionPlayed(tValue);
                    }
                    if (!hasTimeCode) {
                        hasTimeCode = true;
                    }
                    lastReceiveTime = System.currentTimeMillis();
                    publishTimeCode(timeCode);
                }
            }
        }
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    public void oscStatus(OscStatus pOscStatus) {
        // do nothing
    }
    
	public boolean hasTimeCode() {
		return hasTimeCode;
	}

    public boolean isTimeCodeStale() {
        return !hasTimeCode || (System.currentTimeMillis() - lastReceiveTime > STALE_TIME_IN_MS);
    }

    public TimeCode getTimeCode() {
        return timeCode;
    }

    public boolean isTimeCodeValid() {
        return hasTimeCode && timeCode != null;
    }
}
